package com.mikufufu.blog.security.handler;

import com.mikufufu.blog.common.entity.SysUser;
import com.mikufufu.blog.security.enums.RoleCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.AntPathMatcher;

import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Stream;


/**
 * 实现访问决策管理器，用于判断用户是否有权限访问特定资源
 * 
 */
@Slf4j
public class AccessDecisionManagerImpl implements AccessDecisionManager {

    // 使用AntPathMatcher进行URL匹配，检查是否为匿名权限
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    private final String[] anonymousPost = {"/login","/admin/login","/register","/loginByEmail","/admin/loginByEmail"};

    /**
     * 获取匿名访问的权限列表。
     * 匿名权限即不需要用户登录即可访问的资源路径。
     *
     * @return 返回一个包含所有匿名权限路径的列表。
     */
    private String[] anonymousGet(){
        // 初始化匿名权限路径列表，包括默认权限、静态资源权限和Swagger文档权限
        String[] defaultPermission = {"/cache/**","/logout"};
        String[]  staticPermission = {"/","/*.html","/**/*.html","/**/*.js","/**/*.css","/favicon.ico","/image/**"};
        String[]  swaggerPermission ={"/doc.html","/swagger-resources/**","/v2/api-docs","/webjars/**"};
        // 合并权限列表，去除重复项
        return Stream.of(defaultPermission,staticPermission,swaggerPermission).flatMap(Arrays::stream).distinct().toArray(String[]::new);
    }

    /**
     * 判断当前请求是否为匿名访问
     * @param url 请求的URL
     * @param method 请求的方法
     * @return 如果当前请求为匿名访问，则返回true，否则返回false
     */
    private boolean isAnonymous(String url,String method){
        // 遍历匿名访问的权限列表，检查当前URL是否匹配
        for (String anonymous : anonymousGet()){
            if ("GET".equalsIgnoreCase(method) && antPathMatcher.match(anonymous, url)){
                return true;
            }
        }
        for (String anonymous : anonymousPost){
            if ("Post".equalsIgnoreCase(method) && antPathMatcher.match(anonymous, url)){
                return true;
            }
        }
        return false;
    }


    /**
     * 决定是否允许用户访问资源
     * @param authentication 用户认证信息，包含用户的身份验证细节
     * @param object 要访问的资源对象，此处为FilterInvocation，代表一个HTTP请求
     * @param configAttributes 资源的安全配置属性集合，定义了资源的访问权限要求
     * @throws AccessDeniedException 如果用户没有足够的权限访问资源
     * @throws InsufficientAuthenticationException 如果用户认证信息不足
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {

        // 将资源对象转换为FilterInvocation，以便获取请求信息
        FilterInvocation fi = (FilterInvocation) object;
        // 获取请求的URL和方法
        String url = fi.getRequest().getRequestURI();
        String method = fi.getRequest().getMethod();

        if(authentication.getPrincipal() instanceof UserDetails){
            // 如果用户是超级管理员，则允许访问所有资源
            SysUser user = (SysUser) authentication.getPrincipal();
            if (RoleCode.ROLE_SYSTEM_ADMIN.getCode().equals(user.getRole())){
                return;
            }
        }

        // 判断是否为匿名访问的方法,模拟antMatchers
        if (isAnonymous(url,method)){
            return;
        }

        for (ConfigAttribute attribute : configAttributes){
            // 如果配置了接口禁用，则拒绝访问
            if ("disable".equals(attribute.getAttribute())){
                throw new AccessDeniedException("接口被禁用，请联系管理员！！");
            }else if (RoleCode.ROLE_VISITOR.getCode().equals(attribute.getAttribute())){
                // 如果是运行游客访问的资源，则允许访问
                return;
            }
        }

        // 检查用户是否为匿名用户
        if (!(authentication instanceof AnonymousAuthenticationToken)) {
            // 非匿名用户，获取用户角色信息
            SysUser user = (SysUser) authentication.getPrincipal();
//            log.info("用户访问资源：{}", url);

            // 遍历资源的安全配置属性，检查用户是否有访问权限
            if (configAttributes.stream().anyMatch(attribute -> attribute.getAttribute().equals(user.getRole()))){
                return;
            }
        }

        throw new AccessDeniedException("用户没有权限访问该资源");
    }


    /**
     * 判断访问决策管理器是否支持特定的配置属性
     * @param attribute 配置属性
     * @return 总是返回true，表示支持所有配置属性
     */
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    /**
     * 判断访问决策管理器是否支持特定的类
     * @param clazz 需要判断的类
     * @return 如果类是FilterInvocation的子类，则返回true，否则返回false
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

